#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Filename: tapd_export_xmind.py
# Author: ChenChangSong
# Date: 2025-08-24

"""
功能描述: 导出XMind
"""
import sys
import os
import json
import time
# 不再添加项目路径到环境
from comm.common_code import *
from ui_code.common_ui import get_project_root
from tapd_open_api.case.case_get import tcases_get
from tapd_open_api.case.case_number import tcases_get_count
from tapd_capture_api.capture_get_dir_case_count import capture_get_dir_count
from function_code.xmind.generate_xmind import json_to_xmind
from function_code.xmind.ccs_case_list_to_xmind_json import ccs_case_list_to_xmind_json




def tapd_export_xmind(directory_id, output_dir=None):
    """传入一个目录ID获取该目录下的所有目录和该目录下的用例和子目录用例
    输出格式如下:
    [需求ID,用例目录,用例名称,用例步骤,预期结果]
    ['1000062', '产品', '1个目录-细分主题 1', '产品 >> 1个目录', '细分主题 1']
    ['1000062', '产品-TK', '1个目录-细分主题 1', '产品 >> 1个目录', '细分主题 1']
    """
    # 如果没有指定输出目录，则停止
    if output_dir is None:
        logger.error("没有指定输出目录")
        log_to_ui_error("没有指定输出目录")
        return False
    # 读取目录映射和用例数量数据
    # 保存文件的目录
    save_files_dir = get_project_root() / "files" / "save_files"
    
    try:
        with open(save_files_dir / 'tapd_dir_path_name_and_id_dict.json', 'r', encoding='utf-8') as f:
            dir_path_name_and_id_dict = json.load(f)
            logger.info(f"目录路径和id的字典为：{dir_path_name_and_id_dict}")
    except FileNotFoundError as e:
        logger.error(f"配置文件不存在: {e}")
        log_to_ui_error(f"配置文件不存在: {e}")
        return False
    
    ccs_test_case_list = []# 获取到的ccs格式的测试用例，[['1000062', '产品', '1个目录-细分主题 1', '产品 >> 1个目录', '细分主题 1'],['1000062', '产品-TK', '1个目录-细分主题 1', '产品 >> 1个目录', '细分主题 1']]
    un_test_case_id_list = []# 不含 >> 的用例ID
    requirement_id_list = []# 需求ID列表
    
    # =====================================获取目标目录及其所有子目录=====================================
    target_dir_name = None # 目标目录名称
    for name, dir_id in dir_path_name_and_id_dict.items():
        if dir_id == directory_id:
            logger.info(f"目标目录名称为: {name}")
            logger.info(f"目标目录ID为: {dir_id}")
            target_dir_name = name
            break
    
    if target_dir_name is None:
        logger.error(f"未找到ID为 {directory_id} 的目录")
        return False
    
    result = {target_dir_name: directory_id}

    # 获取所有以目标目录开头的子目录
    for dir_name, dir_id in dir_path_name_and_id_dict.items():
        if dir_name.startswith(target_dir_name + "-") and dir_name != target_dir_name:
            result[dir_name] = dir_id

    sorted_result = result
    logger.info(f"获取该目录下的所有目录为: {sorted_result}")
    # =====================================获取导出目录每个目录下的测试用例个数=====================================
    # 先获取一遍目录的数量，避免重复获取
    dir_path_id_and_count = capture_get_dir_count()
    if dir_path_id_and_count is None:
        logger.error("capture_get_dir_count() 返回了 None，无法获取目录数量")
        log_to_ui_error("capture_get_dir_count() 返回了 None，无法获取目录数量")
        return False
    # 遍历sorted_result，目录ID没有在dir_path_id_and_count中则调用tcases_get_count获取用例数量，然后添加到dir_path_id_and_count中
    for dir_name, dir_id in sorted_result.items():
        if dir_id not in dir_path_id_and_count:
            response_code, response = tcases_get_count(dir_id)
            if response_code == 200 and response:
                dir_path_id_and_count[dir_id] = response['data']['count']
            else:
                logger.error(f"目录ID为{dir_id}的目录下的用例数量获取失败")
                log_to_ui_error(f"目录ID为{dir_id}的目录下的用例数量获取失败")
                dir_path_id_and_count[dir_id] = 0


    # =====================================获取每个目录下的测试用例=====================================
    for dir_name, dir_id in sorted_result.items():
        case_count = int(dir_path_id_and_count.get(dir_id, 0))# 返回有字符串{"1148433040001004304":"11","1148433040001004305":13,...}
        # case_count小于200大于1才调用tcases_get，大于200则多次调用（tcases_get每次最多返回200条数据），小于1则跳过
        if case_count < 200 and case_count > 0:
            case_response_code, case_response = tcases_get(dir_id=dir_id)

            # 添加空值检查
            if case_response is None or 'data' not in case_response:
                logger.info(f"目录 {dir_name} 没有用例，跳过处理")
                continue
            
            case_data_list = case_response['data']#  {'status': 1, 'data': [], 'info': 'success'}
            # 添加空列表检查{'status': 1, 'data': [], 'info': 'success'}
            if not case_data_list:
                logger.info(f"目录 {dir_name} 下没有测试用例，跳过")
                continue
            if case_response_code == 200 and case_data_list != []: 
                for case_item in case_data_list:
                    # 确保case_item是字典并且包含'Tcase'键
                    if not isinstance(case_item, dict) or 'Tcase' not in case_item:
                        logger.warning(f"跳过无效的用例数据: {case_item}")
                        continue
                    # 用例ID
                    case_id = case_item['Tcase'].get('id', '') or ''
                    # 用例名称 (手动创建的含有<div>用例名称</div>，需要去除)
                    case_title_text = case_item['Tcase'].get('name', '')                    
                    case_title = case_title_text.replace('<div>', '').replace('</div>', '') 
                    if '-' in case_title:
                            case_title = case_title.replace('-', ' >> ')  # 将-连字符替换为" >> "
                    if '&lt;' in case_title or '&gt;' in case_title:
                        case_title = case_title.replace('&lt;', '<').replace('&gt;', '>')
                    # 前提条件
                    # case_precondition = case_item['Tcase'].get('precondition', '') or ''
                    # 预期结果  
                    case_expect = case_item['Tcase'].get('expectation', '') or '' 
                    if '<div>' in case_expect or '</div>' in case_expect or '&lt;' in case_expect or '&gt;' in case_expect:# 预期结果 (手动创建的含有<div>预期结果</div>)
                        case_expect = case_expect.replace('&lt;', '<').replace('&gt;', '>').replace('<div>', '').replace('</div>', '')
                    # 步骤
                    steps = case_item['Tcase'].get('steps', '') or '' # 获取steps并确保不是None
                    if '&lt;' in steps or '&gt;' in steps:
                        steps = steps.replace('&lt;', '<').replace('&gt;', '>')
                    if not steps or ' >> ' not in steps: # 如果tapd用例步骤为空或不包含 >> 则用例步骤取目录路径+用例名称，预期结果为空
                        un_test_case_id_list.append(case_item['Tcase'].get('id', ''))
                        case_step = dir_name+' >> '+case_title 
                        case_expect = ''
                        if '-' in dir_name:
                            case_step = case_step.replace('-', ' >> ')# 将-连字符替换为" >> "

                    elif ' >> ' in steps: # 如果用例步骤中包含 >> 则用例步骤取用例步骤,预期结果取tapd预期结果
                        case_step = steps
                        
                    ccs_test_case_list.append([case_id, dir_name, case_title, case_step, case_expect])
                    
        elif case_count >= 200:
            for i in range(0, case_count, 200):
                case_response_code, case_response = tcases_get(dir_id=dir_id, page=i//200+1)
                
                # 添加空值检查
                if case_response is None or 'data' not in case_response:
                    logger.info(f"目录 {dir_name} 没有用例，跳过处理")
                    continue
                case_data_list = case_response['data']#  {'status': 1, 'data': [], 'info': 'success'}
                # 添加空列表检查{'status': 1, 'data': [], 'info': 'success'}
                if not case_data_list:
                    logger.info(f"目录 {dir_name} 下没有测试用例，跳过")
                    continue
                if case_response_code == 200 and case_data_list != []: 
                    for case_item in case_data_list:
                        # 确保case_item是字典并且包含'Tcase'键
                        if not isinstance(case_item, dict) or 'Tcase' not in case_item:
                            logger.warning(f"跳过无效的用例数据: {case_item}")
                            continue
                        # 用例ID
                        case_id = case_item['Tcase'].get('id', '') or ''
                        # 用例名称 (手动创建的含有<div>用例名称</div>，需要去除)
                        case_title_text = case_item['Tcase'].get('name', '')                    
                        case_title = case_title_text.replace('<div>', '').replace('</div>', '') 
                        if '-' in case_title:
                                case_title = case_title.replace('-', ' >> ')  # 将-连字符替换为" >> "
                        if '&lt;' in case_title or '&gt;' in case_title:
                            case_title = case_title.replace('&lt;', '<').replace('&gt;', '>')
                        # 前提条件
                        # case_precondition = case_item['Tcase'].get('precondition', '') or ''
                        # 预期结果  
                        case_expect = case_item['Tcase'].get('expectation', '') or '' 
                        if '<div>' in case_expect or '</div>' in case_expect or '&lt;' in case_expect or '&gt;' in case_expect:# 预期结果 (手动创建的含有<div>预期结果</div>)
                            case_expect = case_expect.replace('&lt;', '<').replace('&gt;', '>').replace('<div>', '').replace('</div>', '')
                        # 步骤
                        steps = case_item['Tcase'].get('steps', '') or '' # 获取steps并确保不是None
                        if '&lt;' in steps or '&gt;' in steps:
                            steps = steps.replace('&lt;', '<').replace('&gt;', '>')
                        if not steps or ' >> ' not in steps: # 如果tapd用例步骤为空或不包含 >> 则用例步骤取目录路径+用例名称，预期结果为空
                            un_test_case_id_list.append(case_item['Tcase'].get('id', ''))
                            case_step = dir_name+' >> '+case_title 
                            case_expect = ''
                            if '-' in dir_name:
                                case_step = case_step.replace('-', ' >> ')# 将-连字符替换为" >> "

                        elif ' >> ' in steps: # 如果用例步骤中包含 >> 则用例步骤取用例步骤,预期结果取tapd预期结果
                            case_step = steps
                            
                        ccs_test_case_list.append([case_id, dir_name, case_title, case_step, case_expect])
                        
    logger.info(f"该目录下所有用例:\n {ccs_test_case_list}")


    json_data = ccs_case_list_to_xmind_json(target_dir_name,ccs_test_case_list) # 将ccs格式的测试用例转换为xmind格式
    current_time = time.strftime("%Y%m%d_%H%M%S", time.localtime())
    output_path = os.path.join(output_dir, f"{target_dir_name}_{current_time}.xmind")
    
    # 调用json_to_xmind函数生成XMind文件
    try:
        json_to_xmind(json_data, output_path)  # 传递转换后的xmind_data而不是原始列表
        logger.info(f"成功生成XMind文件: {output_path}")
        log_to_ui_info(f"成功生成XMind文件: {output_path}")
        return True
    except Exception as e:
        logger.error(f"生成XMind文件失败: {e}")
        log_to_ui_error(f"生成XMind文件失败: {e}")
        return False
        



def __sort_dict_by_path(input_dict):
    """
    用例目录字典，按照字典的键（目录路径）进行排序
    """
    tree = {}
    for key, value in input_dict.items():
        parts = key.split('-')
        node = tree
        for part in parts:
            if part not in node:
                node[part] = {}
            node = node[part]
        node['__value__'] = value
    
    # 深度优先遍历收集排序后的键值对
    result = []
    
    def dfs(node, path_parts):
        # 如果当前节点有值，添加到结果
        if '__value__' in node:
            key = '-'.join(path_parts)
            result.append((key, node['__value__']))
        
        # 递归处理所有子节点（按名称排序）
        for child_name in sorted(node.keys()):
            if child_name != '__value__':
                dfs(node[child_name], path_parts + [child_name])
    
    # 从根节点开始遍历
    dfs(tree, [])
    
    # 转换为有序字典
    return dict(result)


if __name__ == '__main__':
    # 示例：指定自定义输出目录
    # 构建配置文件的相对路径
    out_xmind_path_dir = r"files"

    tapd_export_xmind('1148433040001004431', out_xmind_path_dir)
